﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace GpuStructCompiler
{
    class Program
    {
        static void Main(string[] args)
        {
            var document = new Parser().Parse( Lexer.Parse(SystemHeader.Content +File.ReadAllText(args[0])));
            var ofileName = Path.Combine(Path.GetDirectoryName(args[0]), Path.GetFileNameWithoutExtension(args[0]) + "_IO.cl");
            StringBuilder result = new StringBuilder();
            foreach (var s in document.Structs)
            {
                if (SystemHeader.Structs.Contains(s.Name))
                    continue;
                int offset = 0;
                List<VariableDeclare> plainMembers = new List<VariableDeclare>();
                GenerateFunctions(result, document, s, ref offset, s.Name, plainMembers);
                GenerateStructLoadCode(result, s.Name, plainMembers);
                GenerateStructStoreCode(result, s.Name, plainMembers);
            }
            File.WriteAllText(ofileName, result.ToString());
        }

        private static int GetTypeSize(string typeName)
        {
            switch (typeName)
            {
                case "float2":
                case "int2":
                case "uint2":
                    return 2;
                case "float4":
                case "int4":
                case "uint4":
                    return 4; 
            }
            return 1;
        }

        private static void GenerateStructLoadCode(StringBuilder result, string name, List<VariableDeclare> plainMembers)
        {
            int offset = 0;
            result.AppendFormat("struct {0} Load_{0}(__global const float * values, int size, int id)\n", name);
            result.AppendLine("{");
            result.AppendFormat("\tstruct {0} result;\n", name);
            foreach (var m in plainMembers)
            {
                offset = GenerateFieldLoadCode(result, offset, m);
            }
            result.AppendLine("\treturn result;");
            result.AppendLine("}");
        }

        private static int GenerateFieldLoadCode(StringBuilder result, int offset, VariableDeclare m, bool isStruct = true)
        {
            int typeSize = GetTypeSize(m.TypeName);
            if (isStruct)
            {
                if (typeSize == 1)
                    result.AppendFormat("\tresult.{0} = as_{1}(values[{2}*size+id]);\n", m.Name, m.TypeName, offset);
                else
                    result.AppendFormat("\tresult.{0} = *(__global {1} *)(values + {2}*size + id);\n", m.Name, m.TypeName, offset);
            }
            else
            {
                if (typeSize == 1)
                    result.AppendFormat("\treturn as_{1}(values[{2}*size+id]);\n", m.Name, m.TypeName, offset);
                else
                    result.AppendFormat("\treturn *(__global {1} *)(values + {2}*size + id);\n", m.Name, m.TypeName, offset);
            }
            offset += GetTypeSize(m.TypeName);
            return offset;
        }

        private static void GenerateStructStoreCode(StringBuilder result, string name, List<VariableDeclare> plainMembers)
        {
            int offset = 0;
            result.AppendFormat("void Store_{0}(__global float * values, int size, int id, const struct {0} result)\n", name);
            result.AppendLine("{");
            foreach (var m in plainMembers)
            {
                offset = GenerateFieldStoreCode(result, offset, m);
            }
            result.AppendLine("}");
        }

        private static int GenerateFieldStoreCode(StringBuilder result, int offset, VariableDeclare m, bool isStruct = true)
        {
            int typeSize = GetTypeSize(m.TypeName);
            if (isStruct)
                result.AppendFormat("\t*(__global {0} *)(values + {1} * size + id) = result.{2};\n", m.TypeName, offset, m.Name);
            else
                result.AppendFormat("\t*(__global {0} *)(values + {1} * size + id) = result;\n", m.TypeName, offset);            
            offset += typeSize;
            return offset;
        }

        private static void GenerateFunctions(StringBuilder result, Document doc, StructDeclare s,
            ref int offset, string prefix, 
            List<VariableDeclare> plainMembers)
        {
            foreach (var m in s.Members)
            {
                var typeStruct = doc.Structs.Find(x => x.Name == m.TypeName || m.TypeName.EndsWith(x.Name));
                if (typeStruct == null)
                {
                    result.AppendFormat("{0} Load_{1}_{2}(__global const float* values, int size, int id)\n", m.TypeName, prefix, m.Name);
                    result.AppendLine("{");
                    GenerateFieldLoadCode(result, offset, m, false);
                    result.AppendLine("}");

                    result.AppendFormat("void Store_{1}_{2}(__global float* values, int size, int id, const {0} result)\n", m.TypeName, prefix, m.Name);
                    result.AppendLine("{");
                    offset = GenerateFieldStoreCode(result, offset, m, false);
                    result.AppendLine("}");

                    plainMembers.Add(m);
                }
                else
                {
                    List<VariableDeclare> curPlainMembers = new List<VariableDeclare>();
                    int oldOffset = offset; int oldOffset2 = offset;
                    GenerateFunctions(result, doc, typeStruct, ref offset, prefix + "_" + m.Name, curPlainMembers);
                    // load
                    result.AppendFormat("struct {0} Load_{1}_{2}(__global const float* values, int size, int id)\n", m.TypeName, prefix, m.Name);
                    result.AppendLine("{");
                    result.AppendFormat("\tstruct {0} result;\n", m.TypeName);
                    foreach (var pm in curPlainMembers)
                    {
                        plainMembers.Add(new VariableDeclare() { Name = m.Name + "." + pm.Name, TypeName = pm.TypeName });
                        oldOffset = GenerateFieldLoadCode(result, oldOffset, pm);
                    }
                    result.AppendLine("\treturn result;");
                    result.AppendLine("}");
                    // store
                    result.AppendFormat("void Store_{1}_{2}(__global const float* values, int size, int id, const struct {0} result)\n", m.TypeName, prefix, m.Name);
                    result.AppendLine("{");
                    foreach (var pm in curPlainMembers)
                    {
                        oldOffset2 = GenerateFieldStoreCode(result, oldOffset2, pm);
                    }
                    result.AppendLine("}");
                }
            }
        }
    }
}
